package net.thomaswilburn.underground;

import java.util.Collections;
import java.util.List;
import java.util.ListIterator;

import android.app.AlertDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SeekBar;
import android.widget.SimpleCursorAdapter;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.app.ListActivity;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.graphics.Color;

public class Underground extends ListActivity {
	//Constants
	DatabaseHelper sqlHelper;
	SQLiteDatabase db;
	final Context context = this;
	final int DB_ID = 0;
	final int DB_CAPTION = 1;
	final int DB_INTENT = 2; //deprecated - use DB_PACKAGE
	final int DB_PACKAGE = 2;
	final int DB_ACTIVITY = 3; //deprecated - use DB_NAME
	final int DB_NAME = 3;
	final int DB_COLOR = 4;
	final int DB_SORTID = 5;
	final int DB_BUILTIN = 6;
	final int MENU_CHANGETHEME = 0;
	final int MENU_CHANGEHOME = 1;
	final int MENU_ADDITEM = 2;
	final int MENU_GOHOME = 3;
	final int MENU_OPTIONS = 4;
	
	String CFLAG = "NO_CONTEXT_MENU";
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
    	//requestWindowFeature(Window.FEATURE_NO_TITLE);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        LinearLayout root = (LinearLayout) findViewById(R.id.documentroot);
        //At this time, Underground uses just the two basic system themes, for max compatibility
        //So we need to get the preference from the previous session, and init properly.
        //For some reason, we also need to set the root color--the Theme won't do it.
        SharedPreferences prefs = getPreferences(0);
        if (prefs.getString("theme", "white").equals("black")) {
        	setTheme(android.R.style.Theme_Black_NoTitleBar);
        	root.setBackgroundColor(0xFF000000);
        } else {
        	setTheme(android.R.style.Theme_Light_NoTitleBar);
        	root.setBackgroundColor(0xFFFFFFFF);
        }
        registerForContextMenu(getListView());
    }
    
    public void onDestroy() {
    	super.onDestroy();
    }
    
    /*
     * onNewIntent() is where we handle the second Home button press.
     *  
     * (non-Javadoc)
     * @see android.app.Activity#onNewIntent(android.content.Intent)
     */
    protected void onNewIntent(Intent intent) {
    	super.onNewIntent(intent);
 	    if (Intent.ACTION_MAIN.equals(intent.getAction())) {
 	    	//This test is to establish whether we're already in the foreground, and should bow out.
		    if ((intent.getFlags() & Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) != Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT) {
		    	//AppAdapter adapter = (AppAdapter) getListView().getAdapter();
		    	sqlHelper = new DatabaseHelper(this);
		        db = sqlHelper.getWritableDatabase();
		    	Cursor c = db.rawQuery("SELECT * FROM favorites WHERE caption = 'home' AND builtin = 1", null);
		    	c.moveToFirst();
		    	try {
		    		//Attempt to launch an Intent using the stored Home entry in the DB
		    		Intent launch = new Intent(Intent.ACTION_MAIN);
		    		launch.setClassName(c.getString(DB_PACKAGE), c.getString(DB_NAME));
		    		launch.addCategory(Intent.CATEGORY_LAUNCHER);
		    		launch.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		    		startActivity(launch);
		    		//android.os.Process.killProcess(android.os.Process.myPid());
		    		finish();
		    	} catch (Exception error) {
		    		//If it fails, chances are we have the wrong launcher info, so try to update the DB record.
		    		Log.w("UNDERGROUND", error.toString());
		    		updateHomeItem();
		    	}
 	    	}
    	}
    }
    
    /*
     * In onResume() and onPause(), we handle the opening and closing of databases. 
     * I assume this is good practice, because we don't want the database to stay open if the Activiy
     * is killed while backgrounded. While this shouldn't happen to Underground, I wanted to be sure.
     * 
     * (non-Javadoc)
     * @see android.app.Activity#onResume()
     */
    public void onResume() {
    	super.onResume();
    	sqlHelper = new DatabaseHelper(this);
        db = sqlHelper.getWritableDatabase();
        //We are filtering out two menu items--one is the home screen, the other is the legacy 'add new' item.
    	Cursor c = db.rawQuery("SELECT * FROM (SELECT * FROM favorites WHERE builtin != 1 ORDER BY caption ASC) ORDER BY sortID ASC", null);
        ListAdapter dbAdapter = new AppAdapter(this, c);
        setListAdapter(dbAdapter);
    }
    
    public void onPause() {
    	super.onPause();
    	db.close();
    	sqlHelper.close();
    }
    
    public boolean onCreateOptionsMenu(Menu menu) {
    	super.onCreateOptionsMenu(menu);
    	
    	MenuItem adder = menu.add(0, MENU_ADDITEM, 0, "Add item");
    	MenuItem themer = menu.add(0, MENU_CHANGETHEME, 0, "Toggle theme");
    	MenuItem homer = menu.add(0, MENU_CHANGEHOME, 0, "Select launcher");
    	themer.setIcon(android.R.drawable.ic_menu_view);
    	homer.setIcon(android.R.drawable.ic_menu_directions);
    	adder.setIcon(android.R.drawable.ic_menu_add);
    	return true;
    }
    
    /*
     * Take action based on options menu choices.
     * 
     * (non-Javadoc)
     * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
     */
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
    	//Log.w("UNDERGROUND", item.toString());
    	switch (item.getItemId()) {
	    	case MENU_CHANGETHEME:
	    		SharedPreferences prefs = getPreferences(0);
				SharedPreferences.Editor editor = prefs.edit();
	    		if (prefs.getString("theme", "white").equals("white")) {
	    			editor.putString("theme", "black");
	    		} else {
	    			editor.putString("theme", "white");
	    		}
	    		editor.commit();
	    		//Killing the process seems to restart the activity via onCreate()
	    		//This seems to be a practical way to switch themes.
	    		//That said, it seems a bit hacky to me, and may break in a new OS release.
	    		android.os.Process.killProcess(android.os.Process.myPid());
	    		//finish();
	    		return true;
	    	case MENU_CHANGEHOME:
	    		updateHomeItem();
	    		return true;
	    	case MENU_ADDITEM:
	    		pinApplication();
	    		return true;
	    	default:
	    		return false;
    	}
    }
    
    /*
     * onListItemClick() dispatches an Intent to start a new activity, then calls finish() on Underground.
     * 
     * (non-Javadoc)
     * @see android.app.ListActivity#onListItemClick(android.widget.ListView, android.view.View, int, long)
     */
    public void onListItemClick(ListView l, View v, int position, long id) {
		Cursor d = (Cursor) l.getItemAtPosition(position);
    	if (position >= 0) {
			try {
					Intent launch = new Intent(Intent.ACTION_MAIN);
					launch.setClassName(d.getString(DB_PACKAGE), d.getString(DB_NAME));
					launch.addCategory(Intent.CATEGORY_LAUNCHER);
					launch.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
					startActivity(launch);
					finish();
					//android.os.Process.killProcess(android.os.Process.myPid());
			} catch(Exception error) {
				Log.w("UNDERGROUND", error.toString());
				if (d.getString(DB_CAPTION).equalsIgnoreCase("home")) {
					updateHomeItem();
				}
			}
    	}
    }
    
    /*
     * Each list item currently gets two menu items: one to change the tag color, and the other to remove
     * it from the list.
     * 
     * (non-Javadoc)
     * @see android.app.Activity#onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo)
     */
    @Override
    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
    		super.onCreateContextMenu(menu, v, menuInfo);
    		menu.add(0, 0, 0, "Change tag color");
    		menu.add(0, 1, 0, "Remove item");
    }
    
    @Override
    public boolean onContextItemSelected(MenuItem item) {
    	AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
    	Cursor c = (Cursor) getListView().getItemAtPosition(info.position);
    	//Log.w("UNDERGROUND", item.toString());
    	switch ((int) item.getItemId()) {
    	case 0:
    		changeTag(c);
    		return true;
    	case 1:
    		unpin(c);
    		return true;
    	case 2:
    		updateHomeItem();
    		return true;
    	default:
    		return false;
    	}
    }
    
    /*
     * pinApplication() is where things get interesting. Here we get a list of LAUNCH category activities,
     * and then present them to the user for selection. When one is chosen, we pass its information to the 
     * DatabaseHelper and save it in the list.
     */
    public void pinApplication() {
    	final PackageManager p = getPackageManager();
    	Intent filter = new Intent(Intent.ACTION_MAIN);
    	filter.addCategory(Intent.CATEGORY_LAUNCHER);
    	//Build the list of possible activities
    	final List<ResolveInfo> appList = p.queryIntentActivities(filter, 0);
    	//Sort alphabetically.
    	Collections.sort(appList, new android.content.pm.ResolveInfo.DisplayNameComparator(p));
    	//Construct a matching array of CharSequence objects for the list labels
    	final CharSequence[] appOptions = new CharSequence[appList.size()];
    	for (int i = 0; i < appList.size(); i++) {
    		appOptions[i] = (CharSequence) appList.get(i).activityInfo.loadLabel(p).toString();
    	}
    	//Begin building dialog
    	AlertDialog.Builder builder = new AlertDialog.Builder(this);
    	builder.setTitle("Choose application");
    	builder.setItems(appOptions, new DialogInterface.OnClickListener() {
    		//Here's our listener class for when an item is chosen
    		public void onClick(DialogInterface dialog, int item) {
    			ResolveInfo chosen = appList.get(item);
    			AppAdapter adapter = (AppAdapter) getListView().getAdapter();
    			String label = (String) appOptions[item];
    			String packageName = chosen.activityInfo.applicationInfo.packageName.toString();
    			String name = (String) chosen.activityInfo.name.toString();
    			adapter.addItem(label, packageName, name);
    		}
    	});
    	AlertDialog dialog = builder.create();
    	dialog.setOwnerActivity(this);
    	dialog.show();
    }
    
    /*
     * Underground added support for alternative home screens back in 1.1, triggered by the fact that the
     * Nexus One has a new Launcher under a new package namespace. updateHomeItem() checks to see how many home
     * button activities exist (not including itself), and presents a list to the user only if there's more
     * than one option.
     */
    public void updateHomeItem() {
    	final PackageManager p = getPackageManager();
    	Intent filter = new Intent(Intent.ACTION_MAIN);
    	filter.addCategory(Intent.CATEGORY_HOME);
    	//Filter for launcher apps.
    	final List<ResolveInfo> homeList = p.queryIntentActivities(filter, 0);
    	ListIterator<ResolveInfo> iter = homeList.listIterator();
    	//Let's run through the list and remove Underground--no point in making it a choice.
    	while (iter.hasNext()) {
    		ResolveInfo r = iter.next();
    		if (r.activityInfo.loadLabel(p).equals("Underground")) {
    			iter.remove();
    		}
    	}
    	if (homeList.size() > 1) {
    		//if there's an actual choice, go ahead and present that to the user
	    	final CharSequence[] homeOptions = new CharSequence[homeList.size()];
	    	for (int i = 0; i < homeList.size(); i++) {
	    			homeOptions[i] = (CharSequence) homeList.get(i).activityInfo.loadLabel(p).toString();
	    	}
	    	AlertDialog.Builder builder = new AlertDialog.Builder(this);
	    	builder.setTitle("Select Home App");
	    	builder.setItems(homeOptions, new DialogInterface.OnClickListener() {
				
				public void onClick(DialogInterface dialog, int which) {
					ResolveInfo chosen = homeList.get(which);
					AppAdapter adapter = (AppAdapter) getListView().getAdapter();
					String packageName = chosen.activityInfo.applicationInfo.packageName.toString();
					String name = (String) chosen.activityInfo.name.toString();
					adapter.setHome(packageName, name);
				}
			});
	    	AlertDialog dialog = builder.create();
	    	dialog.setOwnerActivity(this);
	    	dialog.show();
	    } else {
	    	//in the case of only one option, simply set it.
	    	ResolveInfo chosen = homeList.get(0);
	    	AppAdapter adapter = (AppAdapter) getListView().getAdapter();
	    	String packageName = chosen.activityInfo.applicationInfo.packageName.toString();
	    	String name = (String) chosen.activityInfo.name.toString();
	    	adapter.setHome(packageName, name);
	    	//Let the user know, so they're not confused.
	    	Toast.makeText(this, "Only one home application found: " + name, Toast.LENGTH_LONG).show();
	    }
    }
    
    /*
     * Underground's tags are a simple way to visually identify a specific entry. We store them in the DB
     * as ARGB integers, then the list adapter can decide what to do with them--either set a view background
     * or tint a drawable. Currently, we do the former for simplicity and speed.
     */
    public void changeTag(Cursor c) {
    	final int id = c.getInt(DB_ID);
    	final int current = c.getInt(DB_COLOR);
    	//final Context context = this;
    	
    	LayoutInflater inflater = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
    	View hsbView = inflater.inflate(R.layout.hsb_view, (ViewGroup) findViewById(R.id.hsbRoot));
    	final View preview = hsbView.findViewById(R.id.hsbColor);
    	final SeekBar seekH = (SeekBar) hsbView.findViewById(R.id.seekH);
    	final SeekBar seekS = (SeekBar) hsbView.findViewById(R.id.seekS);
    	final SeekBar seekB = (SeekBar) hsbView.findViewById(R.id.seekB);
    	
    	//Each SeekBar in the dialog is interactive with the other two, so we'll just use the same
    	//listener class for all three of them.
    	SeekBar.OnSeekBarChangeListener change = new SeekBar.OnSeekBarChangeListener() {

			public void onProgressChanged(SeekBar seekBar, int progress,
					boolean fromUser) {
					if (fromUser) {
						int newColor = Color.HSVToColor(new float[] {((Integer) seekH.getProgress()).floatValue(), ((Integer) seekS.getProgress()).floatValue() / 100, ((Integer) seekB.getProgress()).floatValue() /100});
						preview.setBackgroundColor(newColor);
					}
			}

			public void onStartTrackingTouch(SeekBar seekBar) {
			}

			public void onStopTrackingTouch(SeekBar seekBar) {
			}
    		
    	};
    	seekH.setOnSeekBarChangeListener(change);
    	seekB.setOnSeekBarChangeListener(change);
    	seekS.setOnSeekBarChangeListener(change);
    	
    	//Let's load the current color into the preview pane, and set the sliders accordingly.
    	float[] hsb = {0.0f, 0.0f, 0.0f};
    	Color.colorToHSV(current, hsb);
    	//Log.w("UNDERGROUND", Integer.toHexString(current));
    	seekH.setProgress((int) hsb[0]);
    	seekS.setProgress((int) (hsb[1] * 100));
    	seekB.setProgress((int) (hsb[2] * 100));
    	preview.setBackgroundColor(current);
    	
    	//build the dialog using our constructed view.
    	AlertDialog.Builder builder = new AlertDialog.Builder(this);
    	builder.setTitle("Tag color");
    	builder.setView(hsbView);
    	builder.setPositiveButton("OK", new Dialog.OnClickListener() {

			public void onClick(DialogInterface arg0, int arg1) {
				//Set the color when the OK button is pressed.
				AppAdapter adapter = (AppAdapter) getListView().getAdapter();
				int newColor = Color.HSVToColor(new float[] {((Integer) seekH.getProgress()).floatValue(), ((Integer) seekS.getProgress()).floatValue() / 100, ((Integer) seekB.getProgress()).floatValue() /100});
				adapter.setTagColor(id, newColor);
			}
    		
    	});
    	AlertDialog dialog = builder.create();
    	dialog.setOwnerActivity(this);
    	dialog.show();
    }
    
    /*
     * Unpinning an item is pretty easy. We just toss it from the DB and then requery the list.
     */
    public void unpin(Cursor c) {
    	String toast = "Removed " + c.getString(DB_CAPTION);
    	Toast.makeText(this, toast, Toast.LENGTH_SHORT).show();
    	AppAdapter ref = (AppAdapter) getListView().getAdapter();
    	ref.removeItem(c.getInt(DB_ID));
    }
    
    /*
     * The AppAdapter class contains convenience methods for interfacing with the underlying database,
     * and it also inflates and colors list items during the initial construction.
     */
    class AppAdapter extends SimpleCursorAdapter {
    	ListActivity context;
    	Cursor c;
    	
    	AppAdapter(ListActivity context, Cursor c) {
    		super(context, R.layout.shufflecell, c, new String[] {"caption"}, new int[] {R.id.caption});
    		this.context = context;
    		this.c = c;
    	}
    	
    	public View getView(int position, View convertView, ViewGroup parent) {
    		View row = super.getView(position, convertView, parent);
    		View tag = row.findViewById(R.id.colorstrip);
    		c.moveToPosition(position);
    		//On every other row, we're going to add a slight grey tint.
    		//In the light theme, this darkens it, and in the black theme it lightens it.
    		//If we used custom colors, this should still work over an arbitrary list background.
    		//This stripe effect could be made optional pretty easily.
    		if (position % 2 == 1) {
    			row.setBackgroundColor(0x20808080);
    		} else {
    			row.setBackgroundColor(0x00FFFFFF);
    		}
    		tag.setBackgroundColor(c.getInt(DB_COLOR));
    		//*** This code can be used for changing the color of a drawable instead of a view-based tag.
    		//*** Since I went with a simpler visual approach, we don't use it right now.
    		//Drawable img = context.getResources().getDrawable(R.drawable.white_pin);
    		//pin.setImageDrawable(img);
    		//img.mutate().setColorFilter(c.getInt(DB_COLOR), PorterDuff.Mode.MULTIPLY);
    		return row;
    	}
    	
    	//Convenience method for dropping items
    	public void removeItem(int id) {
    		db.execSQL("DELETE FROM favorites WHERE _id = " + Integer.toString(id));
    		c.requery();
    		notifyDataSetChanged();
    	}
    	
    	//Convenience method for storing new application intent data
    	public void addItem(String caption, String packageName, String activity) {
    		ContentValues values = new ContentValues();
    		values.put("caption", caption.toLowerCase());
    		values.put("package", packageName);
    		values.put("activity_name", activity);
    		values.put("color", 0xFF80FFFF); //Default light green, now that we're HSB for color selection
    		values.put("sortID", 0);
    		values.put("builtin", 0);
    		db.insert("favorites", null, values);
    		c.requery();
    		notifyDataSetChanged();
    	}
    	
    	//Convenience method for setting the built-in home entry
    	public void setHome(String packageName, String name) {
    		ContentValues values = new ContentValues();
    		values.put("package", packageName);
    		values.put("activity_name", name);
    		db.update("favorites", values, "caption = 'home' AND builtin = 1", null);
    	}
    	
    	//Convenience method for updating the tag color of an item.
    	public void setTagColor(int id, int color) {
    		ContentValues values = new ContentValues();
    		values.put("color", color);
    		db.update("favorites", values, "_id = " + Integer.toString(id), null);
    		c.requery();
    		notifyDataSetChanged();
    	}
    	
    }
    
    class DatabaseHelper extends SQLiteOpenHelper {

		public DatabaseHelper(Context context) {
			super(context, "storage.db", null, 2);
		}

		@Override
		public void onCreate(SQLiteDatabase db) {
			db.execSQL("CREATE TABLE favorites (_id INTEGER PRIMARY KEY, caption TEXT, package TEXT, activity_name TEXT, color INTEGER, sortID INTEGER, builtin INTEGER);");
			db.execSQL("INSERT INTO favorites (caption, package, activity_name, color, sortID, builtin) VALUES ('browser', 'com.android.browser', 'com.android.browser.BrowserActivity', 4278255360, 0, 0);");
			db.execSQL("INSERT INTO favorites (caption, package, activity_name, color, sortID, builtin) VALUES ('add new', 'pin', '', 16777215, 1, 1);");
			db.execSQL("INSERT INTO favorites (caption, package, activity_name, color, sortID, builtin) VALUES ('home', 'com.android.launcher', 'com.android.launcher.Launcher', 16777215, 2, 1);");
		}

		@Override
		public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
			if (oldVersion < newVersion) {
				//In version 2 of the database, we changed the name of the table, added some new columns,
				//and changed some field names to be more accurate to the new Intent structure.
				if (newVersion == 2) {
					PackageManager p = getPackageManager();
					Intent query = new Intent(Intent.ACTION_MAIN);
					query.addCategory(Intent.CATEGORY_LAUNCHER);
					List<ResolveInfo> appList = p.queryIntentActivities(query, 0);
					Cursor c = (Cursor) db.rawQuery("select * from pinned_items where (activity is null and builtin = 0)", null);
					c.moveToFirst();
					do {
						boolean foundItem = false;
						for (int i = 0; i < appList.size(); i++) {
							ResolveInfo item = appList.get(i);
							if (item.activityInfo.packageName.equals(c.getString(DB_INTENT))) {
								String label = ((String) item.activityInfo.loadLabel(p)).toLowerCase();
								if (label.equals(c.getString(DB_CAPTION))) {
									ContentValues values = new ContentValues();
									values.put("activity", item.activityInfo.name);
									db.update("pinned_items", values, "_id = ?", new String[] {Integer.toString(c.getInt(DB_ID))});
									foundItem = true;
									Log.w("UNDERGROUND", "Updating item " + c.getString(DB_CAPTION));
									break;
								}
							}
						}
						if (!foundItem) {
							db.delete("pinned_items", "_id = ?", new String[] {Integer.toString(c.getInt(DB_ID))});
							Log.w("UNDERGROUND", "Deleting item " + c.getString(DB_CAPTION));
						}
					} while (c.moveToNext());
					
					db.execSQL("CREATE TABLE favorites (_id INTEGER PRIMARY KEY, caption TEXT, package TEXT, activity_name TEXT, color INTEGER, sortID INTEGER, builtin INTEGER);");
					db.execSQL("INSERT INTO favorites SELECT * FROM pinned_items;");
					db.execSQL("DROP TABLE pinned_items;");
					c.close();
				}
			}
		}
    	
    }
	
}